package com.onpositive.notes.parser;

import java.io.Serializable;
import java.text.NumberFormat;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;

import com.onpositive.data.units.Unit;
import com.onpositive.data.units.Units;

public class AbstractParser implements Serializable {
	
	private static final long serialVersionUID = -5413087235500168941L;

	public AbstractParser() {
		super();
	}

	private static double epsilon = Double.MIN_VALUE * 1000000000;
	
	static private HashMap<Character,Double> fracSet = new HashMap<Character,Double>();
	{		
		fracSet.put('\u00BC', 0.25  );//	1/4
		fracSet.put('\u00BD', 0.5 ) ;
		fracSet.put('\u00BE', 0.75  );//	3/4
		
		fracSet.put('\u2150', 0.142857 );//(142857)	1/7		
		fracSet.put('\u2151', 0.111111   );//(1)	1/9		
		fracSet.put('\u2152', 0.1   );//	1/10
		
		fracSet.put('\u2153', 0.333333   );//(3)	1/3
		fracSet.put('\u2154', 0.666666   );//(6)	2/3
		
		fracSet.put('\u2155', 0.2   );//	1/5
		fracSet.put('\u2156', 0.4   );//	2/5
		fracSet.put('\u2157', 0.6   );//	3/5
		fracSet.put('\u2158', 0.8   );//	4/5
				
		fracSet.put('\u2159', 0.166666 );//(6)	1/6
		fracSet.put('\u215A', 0.833333 );//(3)	5/6
		
		fracSet.put('\u215B', 0.125 );//	1/8
		fracSet.put('\u215C', 0.375 );//	3/8		
		fracSet.put('\u215D', 0.625 );//	5/8		
		fracSet.put('\u215E', 0.875 );//	7/8		
		
		fracSet.put('\u2189', 0.0   );//	0/3 baseball stuff			
	}
	
	public final static String[] delimeters = new String[]{",", "/", "&", "and", "or", ";", "." } ;// add "-" after links audit
	
	protected String preprocess(String s, Map<String, Object> metaInfo)
	{
//		String str = ParserV2.killWikiRefsLeaveRefs(s);
//		str = ParserV2.killWWWLinksAndSingleSquareBrackets(str) ;
		return s;//str ;
	}
	
	public static Block<Double> lookNearestNumberFromStart( String inputString, int beginIndex, int endIndex ){
		
		if( beginIndex > endIndex )
			return null ;
		
		String str = inputString.substring(beginIndex, endIndex) ;		
		boolean gotPoint = false ;
		int l = str.length();
		int end = l, start = -1 ;
		
		Double fracPart = null ;
		boolean isInsideNumber = false, hasDigit = false, gotSlash = false ;
		for( int i = 0 ; i < l ; i++ )
		{
			int curInd = i ;
			char ch = str.charAt(curInd) ;
			if( Character.isDigit(ch) ){
				
				if( !isInsideNumber ){
					start = i ;
					isInsideNumber = true ;
				}
				hasDigit = true ;
				continue ;
			}				
			else if( ch == '.' && !gotPoint )
			{
				boolean pointNeedProcessing = gotDigitsAround( str, curInd) ;
				
				if( !pointNeedProcessing ){					
					if( gotDigitAbove(str, curInd) ){
						boolean positionIsValid = ( ( curInd == 0 )|| !Character.isJavaIdentifierPart( str.charAt(curInd-1) ) ) ;
					
						if(!positionIsValid){
							String preceedingString = str.substring(0, curInd);
							for( String del : delimeters )
							{
								//TODO validate delimeter						
								if( preceedingString.endsWith(del) ){
									positionIsValid = true ;
									break ;
								}
							}
						}
						else
							pointNeedProcessing = positionIsValid ;
						
						pointNeedProcessing = positionIsValid ;
					}
				}
				
				if( pointNeedProcessing ){
					gotPoint = true ;				
					if( !isInsideNumber ){
						start = i ;
						isInsideNumber = true ;
					}					
				}				
				
				continue ;
			}
			else if( ch == ',' && !gotPoint )
			{				
				if( gotDigitsAround( str, curInd) ){
					if( !isInsideNumber ){
						start = i ;
						isInsideNumber = true ;
					}
					continue ;
				}
			}else if( ch == '/' && !gotPoint && isInsideNumber ){				
				if( gotDigitsAround( str, curInd) ){
					gotPoint = true ;
					gotSlash = true ;
					continue ;
				}
				else if( !hasDigit ){
					isInsideNumber = false ;
					gotPoint = false ;
					continue ;
				}
			}
			else if( ch == ' ' ){
				if( !hasDigit )
					isInsideNumber = false ;
				
				if( !isInsideNumber )
					gotPoint = false ;
				
				continue ;
			}
			
			fracPart = fracSet.get(ch) ;
			if( fracPart != null ){
				if( !isInsideNumber )
					start = i ;
					
				end = curInd+1 ;
				break ;
			}
			
			if( hasDigit )
			{				
				end = i ;
				break ;
			}
			else{
				isInsideNumber = false ;
				gotPoint = false ;
			}
		}
		if(!hasDigit)
			return null ;		
		
		for( ;Character.isWhitespace( str.charAt(end-1)); end-- ) ;
		
		try{
			double doubleValue = 0.0 ;
			boolean gotValue = false ;
			if( fracPart == null ){
				int mainPartEnd = end ;
				if( gotSlash ){
					int slashInd = str.indexOf('/',start) ;
					int i0 = slashInd ;
					int i1 = slashInd+1 ;
					for( ; i0 > 0 && Character.isDigit( str.charAt(i0-1)) ; i0-- ) ;
					for( ; i1 < l && Character.isDigit( str.charAt( i1 )) ; i1++ ) ;
					String   numeratorString = str.substring( i0 , slashInd).trim().replace(' ', ',') ;
					String denominatorString = str.substring(slashInd+1, i1).trim().replace(' ', ',') ;
					if( numeratorString.length() != 0 || denominatorString.length() != 0 ){
						try{
							double d0 = NumberFormat.getInstance(Locale.US).parse(  numeratorString).doubleValue();
							double d1 = NumberFormat.getInstance(Locale.US).parse(denominatorString).doubleValue();
							if( d1 > epsilon ){
								double d = d0/d1 ;
								doubleValue += d ;
							}
						}catch(ParseException e){
							e.printStackTrace() ;
							System.out.println( str );
							System.out.println( beginIndex );
						}
					}
					mainPartEnd = i0 ;
					gotValue = true ;
				}
				String valStr = str.substring(start, mainPartEnd).trim().replace(' ', ',') ;
				if( valStr.length() != 0 ){
					doubleValue += NumberFormat.getInstance(Locale.US).parse(valStr).doubleValue();
					gotValue = true ;
				}
				if( !gotValue )
					return null ;
			}
			else{
				String valStr = str.substring(start, end-1).trim().replace(' ', ',') ;
				doubleValue = NumberFormat.getInstance(Locale.US).parse(valStr).doubleValue();
				doubleValue += fracPart ;
			}
			Block<Double> result = new Block<Double>(str, start + beginIndex,end + beginIndex, doubleValue, Block.BLOCK_KIND_SCALAR );
			String substring = inputString.substring(result.getStart(), result.getEnd()) ;
			String trim = substring.trim() ;
			if( !substring.equals(trim))
				return result ;
			else
				return result ;									
						
		}catch( ParseException e ){
			e.printStackTrace() ;
			System.out.println( str );
			System.out.println( beginIndex + ", " + endIndex );
		}
		return null ;
	}

	private static boolean gotDigitsAround(String str, int ind )
	{		
		return gotDigitBelow(str, ind) && gotDigitAbove(str, ind);
	}

	private static boolean gotDigitAbove(String str, int ind)
	{
		int l = str.length() ;
		if( ind < 0 || ind > l-2 )
			return false ;
		
		int post = ind+1 ;
		for(  ; post < l && str.charAt(post) == ' ' ; post++ ) ;
		if( post == l || !Character.isDigit(str.charAt(post)) )
			return false ;

		return true;
	}

	private static boolean gotDigitBelow(String str, int ind)
	{
		int l = str.length() ;
		if( ind < 1 || ind > l-1 )
			return false ;
		
		int prev = ind-1 ;
		for(  ; prev >= 0 && str.charAt(prev) == ' ' ; prev-- ) ;
		if( prev < 0 || !Character.isDigit(str.charAt(prev)) )
			return false ;
		
		return true ;
	}
	
	static protected<T> boolean intersects( Block pcBlock, Collection<Block<T>> blockList)
	{
		if( blockList.isEmpty() )
			return false ;
		
		int blockStart = pcBlock.getStart() ;
		int blockEnd   = pcBlock.getEnd()   ;
		for( Block<?> blk : blockList ){
			int bs = blk.getStart() ;
			int be = blk.getEnd() ;
			
			if( bs >= blockStart && bs < blockEnd )
				return true ;
			
			if( be >= blockStart && be < blockEnd )
				return true ;			
		}
		return false;
	}

	public static Block<Unit> lookUnitInTheEnd( String inputString, int beginIndex, int endIndex, String unitKind ){
		
		if( beginIndex > endIndex )
			return null ;
		
		String str = inputString.substring(beginIndex, endIndex) ;
		int offset = 0, l0 = str.length();
		for( ; offset < l0 && Character.isWhitespace( str.charAt(offset) ) ; offset++ ) ;
		str= str.substring(offset) ;

		ArrayList<Integer> barriers = indicateBarriers(str) ;
		Unit u = null, unit = null ;	
		
		int ind0 = -1, ind1 = barriers.get( barriers.size()-1 ), start = -1, end = -1 ;
		for ( int i = barriers.size()-1 ; i > 0 ; i-- ) {
			
			ind0 = barriers.get(i-1) ;			
			
			String unitName = str.substring(ind0+1,ind1).replaceAll(" +", " ").trim() ;
			if ( !Units.isUnit(unitName) )
				continue ;
			
			u = Units.getUnitByShortName( unitName, unitKind );				
			if( u == null || u.equals(unit) )
				continue ;
				
			start = ind0 + 1 ;
			end = start + unitName.length() ;
			unit = u ;				
		}		
		if( unit == null )
			return null ;
		
		int blockStart = beginIndex + offset + start ;		
		int blockEnd =   beginIndex + offset + end   ;		
		Block<Unit> result = new Block<Unit>( str, blockStart, blockEnd, unit, Block.BLOCK_KIND_UNIT );
		String substring = inputString.substring(result.getStart(), result.getEnd()) ;
		String trim = substring.trim() ;
		if( !substring.equals(trim))
			return result ;
		else
			return result ;	
	}
	
	
	public static  Block<Unit> lookUnitInTheBeginning( String inputString, int beginIndex, int endIndex, String unitKind ){
		
		if( beginIndex > endIndex )
			return null ;
		
		String str = inputString.substring(beginIndex, endIndex) ;
		int offset = 0, l0 = str.length();
		for( ; offset < l0 && Character.isWhitespace( str.charAt(offset) ) ; offset++ ) ;
		str= str.substring(offset) ;
		
		ArrayList<Integer> barriers_ = indicateBarriers(str) ;
		Unit u = null, unit = null ;	
		
		int ind0 = barriers_.get( 0 ), ind1 = -1, start = -1, end = -1 ;
		int size = barriers_.size();
		for ( int i = 1 ; i < size ; i++ ) {
			
			ind1 = barriers_.get(i) ;			
			
			String unitName = str.substring(ind0+1,ind1).replaceAll(" +", " ").trim() ;
			if ( !Units.isUnit(unitName) )
				continue ;
			
			u = Units.getUnitByShortName( unitName, unitKind );				
			if( u == null || u.equals(unit) )
				continue ;
			
			end = ind1 ;
			start = ind1 - unitName.length() ;
			
			unit = u ;				
		}		
		if( unit == null )
			return null ;
		
		int blockStart = beginIndex + offset + start ;		
		int blockEnd =   beginIndex + offset + end   ;		
		Block<Unit> result = new Block<Unit>( str, blockStart, blockEnd, unit, Block.BLOCK_KIND_UNIT );
		String substring = inputString.substring(result.getStart(), result.getEnd()) ;
		String trim = substring.trim() ;
		if( !substring.equals(trim))
			return result ;
		else
			return result ;	
	}
	
	private final static char[] barriers = new char[]{' ', ',', ';', '.', '(', ')', ']', '[', '/', '\n'} ;
	private static ArrayList<Integer> indicateBarriers(String str) {
		
		ArrayList<Integer>  result = new ArrayList<Integer>() ;
		result.add(-1) ;
		int l = str.length() ;
lb0:	for( int i = 0 ; i < l ; i++ )
		{
			char ch = str.charAt(i) ;
			for( char del : barriers )
			{
				if( ch == del ){
					result.add(i) ;					
					continue lb0;
				}
			}
		}
		result.add(l) ;

		return result;
	}
	
	protected String getDefaultComment( String comment ){
		final String className = this.getClass().getSimpleName();
		
		if( comment == null )
			return className ;
		
		StringBuilder bld = new StringBuilder( comment ) ;
		bld.append("(") ;
		bld.append( className ) ;
		bld.append(")") ;
		final String result = bld.toString();
		return result ;
	}
	


}
